/***************************************************************************
 *   Copyright (C) 2008 by Ivan Blagoev Topolsky   *
 *   ivan.topolsky@genebio.com   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Library General Public License as       *
 *   published by the Free Software Foundation; either version 3 of the    *
 *   License, or (at your option) any later version.                       *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this program; if not, write to the                 *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <iostream>
#include <fstream>
#include <iomanip>
#include <string>
#include <algorithm>

// for mkdir
#ifdef _MSC_VER	// MSVC isn't Posix compliant
#include <direct.h>
#define mkdir(D,F) _mkdir(D)
#endif
// MING32 isn't either. But WINE and CYGWIN are POSIX compliant
#if defined(__MINGW32__) && (! defined(__WINE__)) && (! defined(__CYGWIN__)) 
#include <io.h>
#define mkdir(D,F) mkdir(D)
#endif

#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>
#include <errno.h>

// BOOST
#include <boost/regex.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/program_options.hpp>
//#include <boost/tokenizer.hpp>
#include <boost/algorithm/string.hpp>

// windows' minmax conflict with STL's this will avoid it.
#ifndef NOMINMAX 
#define NOMINMAX 1
#endif

// OLE/COM support
#include "comutil.h"
#include "disphelper.h"

#include "analyst.h"


using namespace std;	// STL
using namespace xeno;	// our helpers

#define DEFAULT_EXTENSION ".sml"
#define NONALPHANUM_REPLACE ""



/**
 * call the necessary reading and serializing step to process 1 single sample
 *
 * @param reader	the freud::analyst object used to read
 * @param sample 	which sample to process
 *
 * the filename-related parameters are used when build a file name for output
 * @param oname 	a (complete) output file name
 * @param obase 	the begining of a file name - to be completed using the sample number & name
 */
void dosample(freud::analyst& reader, const int sample, const string& oname = "", const string& obase = "", const bool obaseisdir = FALSE, const bool skipms1 = false, const bool skipms2 = false, const bool dropms1 = false, const bool dropms2 = false, ExtractList *reflist = NULL) {
	using namespace boost;
	
	// read 1 sample
	reader.dosample(sample, NULL, skipms1, skipms2, dropms1, dropms2, reflist);
	reader.buildptr();

	// outname
	string outname;
	if (oname == "") { // if we don't have a name build one using the current sample number and name
		using namespace boost::regex_constants;
		static const regex rx_remove_extension("\\.[^./\\\\]*$", perl);
		static const regex rx_remove_nonalphanum("[^-_.~[:alnum:]]", perl);

		const string samsuf (regex_replace(reader.samplename(),rx_remove_nonalphanum,NONALPHANUM_REPLACE, match_default | format_all));
			// strip all invalid caracters out of sample name
			// only keep : letters, numbers, and a couple of caracters that are both un-escaped in POSIX and tolerated in Windows

		outname = obase + (obaseisdir ? "" : "-") +  lexical_cast<string>(reader.currentsample()) + "-" + samsuf + DEFAULT_EXTENSION;
	} else {
		outname = oname;
	}

	// get data
	bol::RunLcmsms *run = reader.getRun();


	const char *cptr = outname.c_str();
	cout << endl << "Writing to : " << cptr << " ..." << endl;

#ifdef HAVE_PROTOBUF
	bol::protobuf::RunLcmsms pb;

	// operator declared in the include file will take care of populating the google protobuf serializer
	cout << "serializing...";
	pb << *run;
	delete run;
	cout << "done" << endl;
	
	// save data to archive
	cout << "writing...";
	fstream os(cptr, ios::out | ios::trunc | ios::binary);

	if (!pb.SerializeToOstream(&os)) {
		cerr << "Failed to serialize data." << endl
#ifndef __CYGWIN__
			<< endl << "If running from a POSIX shell (like Cygwin's bash) :" << endl
			<< "- please use basic MSDOS-like paths features  ( drive letters + paths)" << endl
			<< "- and no extended POSIX-like features provided by Cygwin ( no symlinks, no mount points, etc. )" << endl
			<< "(as support for Cygwin wasn't compiled in)" << endl
			// If we don't use CYGWIN1.DLL, we won't have support for symlinks, mount points and other POSIX features.
#endif
		;
	} else {
		cout << "done." << endl;
	}
#else
#error		"google protocole buffer serialisation isn't compiled in"
#endif
}



int main(int argc, char *argv[]) {
	cout << "SmileWiff - An Analyst Wiff converter for SmileMS" << endl << endl;
	
	/*******************
	*                  *
	*   COMMAND LINE   *
	*                  *
	*******************/
	
	string ifname ("");
	string ofname ("");
	string ofbase ("");
	string runname ("");
	int sample = 0;
	bool skipms1 = false, skipms2 = false;
	bool dropms1 = false, dropms2 = false;
	bool obaseisdir = FALSE;
	
	using namespace boost;
	using namespace boost::program_options;

	// Declare the supported options.
	options_description desc("Usage:\n\tsmilewiff [ --options [...]] [input file] [output file]\n\nAllowed options");
	desc.add_options()
		("help,h", "produce help message")
		("input-file,i", value< string >(&ifname), "input file name\ncan be specified after the options, too")
		("output-file,o", value< string >(&ofname), "output file name for one single sample\ncan be specified after the options, too")
		("output-basename,b", value< string >(&ofbase), "base for output file name - sample name will be appended\nuseful when processing more than one single sample")
		("sample-info",	"print out information about samples only")
		("sample,s",	value< int >(&sample),	"select which sample from a wiff file to read")
		("skip-peak", value< string >(), "choose which peaks to skip : none, ms1, ms2 or all")
		("skip-all", value< string >(), "choose which collection to skip completely (peaks and structure) : none, ms1, ms2 or all")
		;
	
	positional_options_description pos;
	pos.add("input-file", 1);
	pos.add("output-file", 1);

	variables_map vm;

	// Parse the command line
	try {
		store(command_line_parser(argc, argv).options(desc).positional(pos).run(), vm);
		notify(vm);
	} catch (exception& e) {
		cerr	<< "Error while parsing options :" << endl
			<< e.what() << endl;
		exit(3);
	} catch (...) {
		cerr	<< "Error while parsing options." << endl;
		exit(3);
	}



	if (vm.count("help")) {
		cout << desc << endl;
		exit(0);
	}

	if (! vm.count("input-file")) {
		cerr << "Please at least give an input file name" << endl
			<< endl << desc << endl;
		exit(3);
	}
	
	if (sample) {
		cout << "Processing sample " 
			<< sample <<  endl;
	} else {
		cout << "Process all samples" << endl;
		if (vm.count("output-file")) {
			cerr << "One output file can only hold one single sample" << endl
				<< "- either specify sample number to process" << endl
				<< "- or specify an base for output filename to which the sample name will be appended" << endl
				<< "- or don't specify an output file name and let the software generate names" << endl
				<< desc << endl;
			exit(1);
		}
	}

	if (vm.count("skip-all")) {
		string skipall(vm["skip-all"].as<string>());
		dropms1 = ((skipall == "ms1") || (skipall == "all")) ? true : false;
		dropms2 = ((skipall == "ms2") || (skipall == "msn") || (skipall == "all")) ? true : false;
		cout	<< "All data for MS :\t" << (dropms1 ? "skip" : "fetch") << endl
				<< "All data for MS^n :\t" << (dropms2 ? "skip" : "fetch") << endl;
	}
	if (vm.count("skip-peak")) {
		string skippeak (vm["skip-peak"].as<string>());
		skipms1 = ((skippeak == "ms1") || (skippeak == "all")) ? true : false;
		skipms2 = ((skippeak == "ms2") || (skippeak == "msn") || (skippeak == "all")) ? true : false;
		cout	<< "Peak data for MS :\t" << ((skipms1 || dropms1) ? "skip" : "fetch") << endl
				<< "Peak data for MS^n :\t" << ((skipms2 || dropms2) ? "skip" : "fetch") << endl;
	}

	if ((! vm.count("output-file")) && (! vm.count("output-basename"))) {
		using namespace boost;
		using namespace boost::regex_constants;
		
		// build a base name
		static const regex rx_remove_extension("\\.[^./\\\\]*$", perl);
		ofbase = regex_replace(ifname,rx_remove_extension,"/", match_default);
		obaseisdir = TRUE;
		
		// the default base name is a directory. attempt to create it.
		if (mkdir(ofbase.c_str(), 0775)) {
			if (errno == EEXIST) {
				cerr << "Warning, directory " << ofbase << " already exists." << endl;
			} else {
				cerr << "Error while creating directory ";
				perror(ofbase.c_str());
				exit (1);
			}
		}
	}

	/*********************
	*                    *
	*   INITIALISATION   *
	*                    *
	*********************/

	//
	// Never forget to initialise first otherwise "800401f0 - CoInitialize has not been called."
 	//
	switch (CoInitializeEx(NULL, COINIT_MULTITHREADED)) { // COINIT_SPEED_OVER_MEMORY COINIT_APARTMENTTHREADED COINIT_MULTITHREADED
		case S_OK:
			cout << "COM initialised" << endl;
			break;
		case S_FALSE:
			cout << "COM already initialised in this thread" << endl;
			break;
#ifdef RPC_E_CHANGED_MOD
		case RPC_E_CHANGED_MOD:
			cerr << "different concurrency model  or neutral threaded apartment" << endl;
			exit(2);
#endif
		case E_INVALIDARG:
			cerr << "invalid arg" << endl;
			exit(2);
		case E_OUTOFMEMORY:
			cerr << "out of memory" << endl;
			exit(2);
		case E_UNEXPECTED:
			cerr << "unexpected error" << endl;
			exit(2);
		default:
			cerr << "unkown COM initialisation failure" << endl;
			exit(2);
	};
	
	dhInitialize(FALSE);


// for debugging purpose :
//	dhToggleExceptions(TRUE);	// this will cause error to make nice messages boxes



	/***************
	*              *
	*   GET DATA   *
	*              *
	***************/
	
	{
		freud::analyst reader;	// Look, ma : Namespace Pun !!!

		// init the reader, and open the file
		reader.initfile(ifname.c_str());
		// list samples
		reader.samplelist();
		if (vm.count("sample-info")) {
			// NOTE samples are in 'reader.samplename(n)' and could be further used
			cout << endl;
			dhUninitialize(TRUE);
			exit(0);
		}

		
	
		// process all selected sample
		cout.setf ( ios::fixed );
		cout.unsetf(ios::showpoint);
		cout << setprecision(2);
		if (sample) {
			dosample(reader, sample, ofname, ofbase, obaseisdir, skipms1, skipms2, dropms1, dropms2);
		} else {
			for (int s = 1; s <= reader.numsamples(); s++) {
				dosample(reader, s, ofname, ofbase, obaseisdir, skipms1, skipms2, dropms1, dropms2);
				cout <<  endl;
			}
		}

		// to manually inspect output :
		//  protoc --decode=bol.protobuf.RunLcmsms runlcmsms.proto < {ofname}  | less
		cout <<  endl;
	}

	dhUninitialize(TRUE);
	
	cout << endl << "Finished." << endl << endl;
	exit(0);
}

